---
title: COBE
---

import createGlobe from "cobe";
import { useEffect, useRef } from "react";
import { useSpring } from 'react-spring';

export function Cobe() {
  const canvasRef = useRef();
  const pointerInteracting = useRef(null);
  const pointerInteractionMovement = useRef(0);
  const [{ r }, api] = useSpring(() => ({
    r: 0,
    config: {
      mass: 1,
      tension: 280,
      friction: 40,
      precision: 0.001,
    },
  }));
  useEffect(() => {
    let phi = 0;
    let width = 0;
    const onResize = () => canvasRef.current && (width = canvasRef.current.offsetWidth)
    window.addEventListener('resize', onResize)
    onResize()
    const globe = createGlobe(canvasRef.current, {
      devicePixelRatio: 2,
      width: width * 2,
      height: width * 2,
      phi: 0,
      theta: 0.2,
      dark: 1.1,
      diffuse: 3,
      mapSamples: 16000,
      mapBrightness: 1.8,
      mapBaseBrightness: .05,
      baseColor: [1.1, 1.1, 1.1],
      markerColor: [251 / 255, 100 / 255, 21 / 255],
      glowColor: [1.1, 1.1, 1.1],
      markers: [
        // San Francisco, default color
        { location: [37.7595, -122.4367], size: 0.05 },
        // New York, red color
        { location: [40.7128, -74.006], size: 0.05, color: [1, 0, 0] },
        // Tokyo, blue color
        { location: [35.6895, 139.6917], size: 0.05, color: [0, 0.5, 1] },
        // Sydney, green color
        { location: [-33.8688, 151.2093], size: 0.05, color: [0, 1, 0] },
        // Rio de Janeiro, purple color
        { location: [-22.9068, -43.1729], size: 0.05, color: [0.8, 0, 0.8] },
        // Paris, yellow color
        { location: [48.8566, 2.3522], size: 0.05, color: [1, 1, 0] },
        // Porto, orange color
        { location: [41.1579, -8.6291], size: 0.05, color: [1, 0.5, 0] },
        // Athens, pink color
        { location: [37.9838, 23.7275], size: 0.05, color: [1, 0.5, 1] },
        // Rome, brown color
        { location: [41.9028, 12.4964], size: 0.05, color: [0.5, 0.3, 0] },
        // Kathmandu, blue color
        { location: [27.7172, 85.324], size: 0.05, color: [0, 0.5, 1] },
        // Tarbes, green color
        { location: [43.4643, -0.5167], size: 0.05, color: [0, 1, 0] },
        // Bamako, yellow color
        { location: [12.6683, -8.0076], size: 0.05, color: [1, 1, 0] },
        // Djibouti, purple color
        { location: [11.5500, 43.1667], size: 0.05, color: [0.8, 0, 0.8] },
      ],
      opacity: .7,
      onRender: (state) => {
        // Called on every animation frame.
        // `state` will be an empty object, return updated params.
        state.phi = phi + r.get()
        phi += 0.005
        state.width = width * 2
        state.height = width * 2
      }
    })
    setTimeout(() => canvasRef.current.style.opacity = '1')
    return () => globe.destroy()
  }, [])
  return <div style={{
    width: '100%',
    maxWidth: 600,
    aspectRatio: 1,
    margin: 'auto',
    position: 'relative',
  }}>
    <div style={{
      width: '100%',
      fontWeight: 700,
      top: '50%',
      transform: 'translateY(-50%)',
      zIndex: 1,
      textAlign: 'center',
      color: '#fff',
      pointerEvents: 'none',
      userSelect: 'none',
      position: 'absolute',
      mixBlendMode: 'difference'
    }}>
      <h1 style={{
        fontSize: 'min(9vw,3.2em)',
        letterSpacing: '.3em',
        textIndent: '.3em',
        margin: 'auto',
      }}>COBE</h1>
      <span style={{ fontSize: '1.2em' }}>5kB WebGL Globe</span>
    </div>
    <canvas
      ref={canvasRef}
      onPointerDown={(e) => {
        pointerInteracting.current =
          e.clientX - pointerInteractionMovement.current;
        canvasRef.current.style.cursor = 'grabbing';
      }}
      onPointerUp={() => {
        pointerInteracting.current = null;
        canvasRef.current.style.cursor = 'grab';
      }}
      onPointerOut={() => {
        pointerInteracting.current = null;
        canvasRef.current.style.cursor = 'grab';
      }}
      onMouseMove={(e) => {
        if (pointerInteracting.current !== null) {
          const delta = e.clientX - pointerInteracting.current;
          pointerInteractionMovement.current = delta;
          api.start({
            r: delta / 200,
          });
        }
      }}
      onTouchMove={(e) => {
        if (pointerInteracting.current !== null && e.touches[0]) {
          const delta = e.touches[0].clientX - pointerInteracting.current;
          pointerInteractionMovement.current = delta;
          api.start({
            r: delta / 100,
          });
        }
      }}
      style={{
        width: '100%',
        height: '100%',
        cursor: 'grab',
        contain: 'layout paint size',
        opacity: 0,
        transition: 'opacity 1s ease',
        borderRadius: '50%',
      }}
    />
  </div>
}

<Cobe/>

import Link from 'next/link'

<div style={{ textAlign: 'center' }}>
  <div className="inline-flex justify-center cta-container">
    <Link href="/docs">
      <a className="cta" draggable="false">Get Started</a>
    </Link>
    <a href="https://cobe-playground.vercel.app" className="cta" draggable="false">Playground</a>
  </div>
  <div style={{ marginTop: '2rem' }}/>
  <a href="https://twitter.com/shuding_/status/1475916082875666441" target="_blank" style={{
    color: 'inherit',
    opacity: .5,
    fontSize: 14
  }}>How the library was created →</a>
</div>

<style jsx global>{`
body {
  background: linear-gradient(
      180deg,
      hsla(0, 0%, 100%, 0) 0,
      #fff min(110vw, 800px)
    ),
    fixed
      repeating-radial-gradient(
        circle at 50% min(calc(50vw + 3.5rem), calc(300px + 5rem)),
        #f5f5f5 0,
        #fff 40px
      );
}
.dark body {
  background: linear-gradient(
    0deg,
    hsla(0, 0%, 100%, 0.2) 0,
    #000 min(110vw, 800px)
  ),
  fixed
    repeating-radial-gradient(
      circle at 50% min(calc(50vw + 3.5rem), calc(300px + 5rem)),
      #050505 0,
      #000 40px
    );
}
`}</style>
